package model

import (
	"context"
	"database/sql"
	"fmt"
	"time"

	"github.com/zeromicro/go-zero/core/stores/cache"
	"github.com/zeromicro/go-zero/core/stores/sqlc"
	"github.com/zeromicro/go-zero/core/stores/sqlx"
	"github.com/zeromicro/go-zero/core/stringx"
)

// 表字段名
var userRows = "`id`,`uuid`,`username`,`password`,`email`,`avatar`,`gender`,`brief`,`status`,`last_login_ip`,`last_login_at`,`created_at`,`updated_at`,`deleted_at`"

// 更新占位符
var userRowsWithPlaceHolder = "`uuid`=?,`username`=?,`password`=?,`email`=?,`avatar`=?,`gender`=?,`brief`=?,`status`=?,`last_login_ip`=?,`last_login_at`=?,`deleted_at`=?"

var (
	userFieldNames          = stringx.Remove(userRows, "`id`")
	cacheUserIdPrefix       = "cache:user:id:"
	cacheUserUuidPrefix     = "cache:user:uuid:"
	cacheUserUsernamePrefix = "cache:user:username:"
	cacheUserEmailPrefix    = "cache:user:email:"
)

// 错误定义
var ErrNotFound = sqlc.ErrNotFound

type (
	UserModel interface {
		Insert(ctx context.Context, data *User) (sql.Result, error)
		FindOne(ctx context.Context, id int64) (*User, error)
		FindOneByUuid(ctx context.Context, uuid string) (*User, error)
		FindOneByUsername(ctx context.Context, username string) (*User, error)
		FindOneByEmail(ctx context.Context, email string) (*User, error)
		Update(ctx context.Context, data *User) error
		Delete(ctx context.Context, id int64) error
	}

	defaultUserModel struct {
		sqlc.CachedConn
		table string
	}

	User struct {
		Id          int64      `db:"id"`            // 用户ID
		Uuid        string     `db:"uuid"`          // 用户UUID
		Username    string     `db:"username"`      // 用户名
		Password    string     `db:"password"`      // 密码
		Email       string     `db:"email"`         // 邮箱
		Avatar      string     `db:"avatar"`        // 头像URL
		Gender      int8       `db:"gender"`        // 性别: 0未知, 1男, 2女
		Brief       string     `db:"brief"`         // 个人简介
		Status      int8       `db:"status"`        // 状态: 0禁用, 1启用
		LastLoginIp string     `db:"last_login_ip"` // 最后登录IP
		LastLoginAt *time.Time `db:"last_login_at"` // 最后登录时间
		CreatedAt   time.Time  `db:"created_at"`    // 创建时间
		UpdatedAt   time.Time  `db:"updated_at"`    // 更新时间
		DeletedAt   *time.Time `db:"deleted_at"`    // 删除时间
	}
)

func NewUserModel(conn sqlx.SqlConn, c cache.CacheConf) UserModel {
	return &defaultUserModel{
		CachedConn: sqlc.NewConn(conn, c),
		table:      "`user`",
	}
}

func (m *defaultUserModel) Insert(ctx context.Context, data *User) (sql.Result, error) {
	query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, userFieldNames)
	ret, err := m.ExecNoCacheCtx(ctx, query, data.Uuid, data.Username, data.Password, data.Email,
		data.Avatar, data.Gender, data.Brief, data.Status, data.LastLoginIp, data.LastLoginAt, data.DeletedAt)

	return ret, err
}

func (m *defaultUserModel) FindOne(ctx context.Context, id int64) (*User, error) {
	userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id)
	var resp User
	err := m.QueryRowCtx(ctx, &resp, userIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error {
		query := fmt.Sprintf("select %s from %s where `id` = ? and `deleted_at` is null limit 1", userRows, m.table)
		return conn.QueryRowCtx(ctx, v, query, id)
	})
	switch err {
	case nil:
		return &resp, nil
	case sqlc.ErrNotFound:
		return nil, ErrNotFound
	default:
		return nil, err
	}
}

func (m *defaultUserModel) FindOneByUuid(ctx context.Context, uuid string) (*User, error) {
	userUuidKey := fmt.Sprintf("%s%v", cacheUserUuidPrefix, uuid)
	var resp User
	err := m.QueryRowCtx(ctx, &resp, userUuidKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error {
		query := fmt.Sprintf("select %s from %s where `uuid` = ? and `deleted_at` is null limit 1", userRows, m.table)
		return conn.QueryRowCtx(ctx, v, query, uuid)
	})
	switch err {
	case nil:
		return &resp, nil
	case sqlc.ErrNotFound:
		return nil, ErrNotFound
	default:
		return nil, err
	}
}

func (m *defaultUserModel) FindOneByUsername(ctx context.Context, username string) (*User, error) {
	userUsernameKey := fmt.Sprintf("%s%v", cacheUserUsernamePrefix, username)
	var resp User
	err := m.QueryRowCtx(ctx, &resp, userUsernameKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error {
		query := fmt.Sprintf("select %s from %s where `username` = ? and `deleted_at` is null limit 1", userRows, m.table)
		return conn.QueryRowCtx(ctx, v, query, username)
	})
	switch err {
	case nil:
		return &resp, nil
	case sqlc.ErrNotFound:
		return nil, ErrNotFound
	default:
		return nil, err
	}
}

func (m *defaultUserModel) FindOneByEmail(ctx context.Context, email string) (*User, error) {
	userEmailKey := fmt.Sprintf("%s%v", cacheUserEmailPrefix, email)
	var resp User
	err := m.QueryRowCtx(ctx, &resp, userEmailKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error {
		query := fmt.Sprintf("select %s from %s where `email` = ? and `deleted_at` is null limit 1", userRows, m.table)
		return conn.QueryRowCtx(ctx, v, query, email)
	})
	switch err {
	case nil:
		return &resp, nil
	case sqlc.ErrNotFound:
		return nil, ErrNotFound
	default:
		return nil, err
	}
}

func (m *defaultUserModel) Update(ctx context.Context, data *User) error {
	userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, data.Id)
	userUuidKey := fmt.Sprintf("%s%v", cacheUserUuidPrefix, data.Uuid)
	userUsernameKey := fmt.Sprintf("%s%v", cacheUserUsernamePrefix, data.Username)
	userEmailKey := fmt.Sprintf("%s%v", cacheUserEmailPrefix, data.Email)
	_, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
		query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, userRowsWithPlaceHolder)
		return conn.ExecCtx(ctx, query, data.Uuid, data.Username, data.Password, data.Email,
			data.Avatar, data.Gender, data.Brief, data.Status, data.LastLoginIp, data.LastLoginAt, data.DeletedAt, data.Id)
	}, userIdKey, userUuidKey, userUsernameKey, userEmailKey)
	return err
}

func (m *defaultUserModel) Delete(ctx context.Context, id int64) error {
	user, err := m.FindOne(ctx, id)
	if err != nil {
		return err
	}

	userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id)
	userUuidKey := fmt.Sprintf("%s%v", cacheUserUuidPrefix, user.Uuid)
	userUsernameKey := fmt.Sprintf("%s%v", cacheUserUsernamePrefix, user.Username)
	userEmailKey := fmt.Sprintf("%s%v", cacheUserEmailPrefix, user.Email)
	_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
		// 软删除
		now := time.Now()
		query := fmt.Sprintf("update %s set `deleted_at` = ? where `id` = ?", m.table)
		return conn.ExecCtx(ctx, query, now, id)
	}, userIdKey, userUuidKey, userUsernameKey, userEmailKey)
	return err
}
